#!/usr/bin/env python
#
# Copyright 2011 Justin Bruce Van Horne <justinvh@gmail.com>
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

""" Handles the physical map construction for the levels.

 MapEditor: The map editor allows construction of maps to be loaded in LOZ.
"""

import sys
import os
import pyglet
import json

from pyglet.window import Window, key
from pyglet.graphics import Batch
from pyglet.clock import schedule_interval
from pyglet.gl import *
from pyglet.sprite import Sprite
from pyglet.image import load as load_image
from pyglet.text import Label as PygletLabel

from engine.fixed_resolution import FixedResolutionViewport
from engine.material import Material
from engine.pyglet_extensions import TileableSprite
from engine import camera

from ui import *
from shared import *

from time import time

WIDTH, HEIGHT = 384 * 2, 320 * 2

_MOD_SHIFT = 65505L
_MOD_CTRL = 65507L

LOCK_FILE = '.loz-map-editor.lck'
AUTOSAVE_MAP = '.loz-map-editor-autosave.map'
AUTOSAVE_TIME = 15
CONFIG_PATH = '.loz-map-editor.cfg'

class MapEditor(Window):
    '''The map editor allows the user to construct new tiles on
    the screen. It is capable of interacting with a preview,
    texture, and level manager.'''
    # Handles all the tile selection and configuration
    # from the texture browser.
    selected = None
    map_width, map_height = 0, 0
    dragging = False
    inserting_tile = None
    inserting_entity = None
    inserting_conf = None
    inserting_index = 0
    selected_tile_index = 0
    last_tile = None
    group = pyglet.graphics.OrderedGroup(0)
    _group = 0
    _mtr = {}
    _entities = {}

    # Meta info
    _possible_drag = False
    _undo_actions = []
    _redo_actions = []

    # Pan information
    pan_sens = 40
    pan_size = 16
    pan_enable = True
    pan_free = False
    _pan_scheduled = False

    # For preserving mouse coordinates
    mx, my = 100, 100

    # Grid heights and dirtiness
    active_height = 0
    grid_opacity = 150
    _grid_dirty = False
    snap = True

    # Active menus
    menu = None

    # Rendering batches and available tiles
    batches = [[True, Batch()] for x in range(10)]
    tiles = []

    def save_map(self, map_name):
        import Image
        fmt = "RGBA"
        tile_map = map_name + '-optimized-tile.png'

        print 'Preparing to save %s' % map_name

        print 'Generating %dpx x %dpx canvas...' % (self.map_width, self.map_height),

        canvas = Image.new(fmt, (self.map_width * 2, self.map_height * 2))
        print 'Okay!'

        cleaned = []

        def grouping_cmp(fst, snd):
            return fst[0].group.order < snd[0].group.order

        print 'Sorting tiles for rendering priority...',
        self.tiles = sorted(self.tiles, grouping_cmp)
        total = len(self.tiles)
        print 'Sorted %d tiles!' % total

        print 'Creating tile map from %d tiles...' % (total),
        for n, data in enumerate(self.tiles):
            if data is not None:
                tile, conf = data
                cleaned.append(conf)
                tile_image = tile.image.get_image_data()
                pitch = -(tile_image.width * 4)
                data = tile_image.get_data(fmt, pitch)
                im = Image.fromstring(fmt, (tile.width, tile.height), data)
                im = im.convert("RGBA")
                r,g,b,a = im.split()

                tile.x += self.map_width
                tile.y += self.map_height

                if tile.width != tile.tile_width or tile.height != tile.tile_height:
                    x, y, w, h  = tile.x, tile.y, abs(tile.tile_width), abs(tile.tile_height)
                    t, l, b, r = y, x, y - h*2, x + w
                    for i in range(l, r + tile.width, tile.width):
                        for j in range(b, t + tile.height, tile.height):
                            canvas.paste(im, (i, j), mask=a)
                else:
                    canvas.paste(im=im, box=(tile.x, tile.y), mask=a)
        canvas.save(tile_map)
        print 'Generated tile map to %s' % tile_map

        print 'Generating map to %s' % map_name,
        json.dump(cleaned, open("%s" % (map_name), "w"), indent=4)
        print 'Good!'

    

    def _autosave_scheduler(self, dt):
        print 'Autosaving...'
        self.save_map(AUTOSAVE_MAP)

    def _pan_scheduler(self, dt):
        if (self.pan_enable and self.keys[_MOD_CTRL]) or self.pan_free:
            if (self.width - self.mx) < self.pan_sens:
                self.camera.x -= self.pan_size
                self.grid.x += self.pan_size
                self.preview_grid.tile_width += self.pan_size
            elif self.mx < self.pan_sens:
                self.camera.x += self.pan_size
                self.grid.x -= self.pan_size
                if self.preview_grid.x <= self.camera.x:
                    self.preview_grid.x -= self.width * 2
                    self.preview_grid.tile_width += self.width * 2

            if (self.height - self.my) < self.pan_sens:
                self.camera.y -= self.pan_size
                self.grid.y += self.pan_size
                self.preview_grid.tile_height += self.pan_size
            elif self.my < self.pan_sens:
                self.camera.y += self.pan_size
                self.grid.y -= self.pan_size


    def _save_action(self, text):
        """Handles the logic for saving a map"""
        self.frame.remove(self.save_dialogue)
        self.save_map(text.text)
        
    def load_map(self, map_name):
        try:
            tiles = json.load(open("%s" % map_name))
            map(self.insert_tile_dict, tiles)
            print 'Loading %s tiles.' % len(tiles)
            print 'Map size: %d x %d' % (self.map_width, self.map_height)
        except IOError, e:
            print '%s was not found: %s' % (map_name, e)

    def _load_action(self, text):
        """Handles the logic for loading a map"""
        self.frame.remove(self.load_dialogue)
        self.load_map(text.text)
        
    def _generate_entity_list(self):
        def _generate(entity):
            def _local(button):
                # Setup our new entity
                tile_img = self._mtr[entity].image
                tile_conf = self._mtr[entity].name
                batch = self.batches[self.active_height][1]
                self.inserting_entity = TileableSprite(tile_img,
                        0, 0, group=self.group, batch=batch)
                self.inserting_entity.opacity = 155
                # Close the dialog
                if self.dialogue.parent:
                    self.frame.remove(self.dialogue)
                # Update the mouse position
                self.on_mouse_motion(self.mx, self.my, 0, 0)

            return _local
        lst = []
        for i in self._entities:
            lst.append(Label(i, action=_generate(i)))
        return lst

    def _set_preview_grid_visibility(self, checkbox):
        """Sets the visibility of the grid based off of a checkbox object"""
        self.preview_grid.visible = checkbox.value

    def toggle_preview(self, on_off=None):
        if self.preview_window:
            if on_off is None:
                on_off = not self.preview_window._visible
            self.preview_window.set_visible(on_off)
            x, y = self.get_location()
            self.preview_window.set_location(self.width + x + 20, y + 320)

    def _toggle_preview(self, checkbox):
        """Toggles the preview window if available"""
        self.toggle_preview(checkbox.value)

    def _grid_toggle_snap(self, checkbox):
        """Toggles the snapping of a grid based off of the menu."""
        if self._grid_dirty:
            checkbox.value = self.snap

    def _grid_toggle_update(self, checkbox):
        """Forces the update of the grid."""
        if self._grid_dirty:
            checkbox.value = self.grid.visible

    def _grid_toggle_pan(self, checkbox):
        """Forces the update of the grid."""
        checkbox.value = self.pan_free

    def _grid_opacity_update(self, slider):
        """Updates the opacity in the grid."""
        if self._grid_dirty:
            slider.value = self.grid.opacity

    def _grid_size_update(self, slider):
        """Called as a think() for the slider."""
        if self._grid_dirty:
            slider.value = self.grid_index

    def _set_grid_visibility(self, checkbox):
        """Toggles the grid visibility based off of a checkbox value."""
        self.grid.visible = checkbox.value

    def _set_pan_free(self, checkbox):
        """Sets the pan freely value based off of a checkbox value."""
        self.pan_free = checkbox.value

    def _set_grid_snap(self, checkbox):
        """Sets the snap value based off of a checkbox value."""
        self.snap = checkbox.value

    def _set_grid_opacity(self, slider):
        """Sets the grid opacity based off of a slider value."""
        self.grid.opacity = round(slider.value)

    def _set_grid_size(self, slider):
        """Sets the grid size based off of a slider value."""
        self.grid_index = int(round(slider.value))
        slider.value = self.grid_index
        self.grid.image = self.grids[self.grid_index]

    def _new_map(self, button):
        print 'New'

    def _load_map(self, button):
        """Dialogue action for loading a map."""
        if self.load_dialogue.parent:
            self.frame.remove(self.dialogue)
        self.frame.add(self.load_dialogue)
        self.load_dialogue._set_x(self.mx)
        self.load_dialogue._set_y(self.my)

    def _save_map(self, button):
        """Dialogue action for saving a map."""
        if self.save_dialogue.parent:
            self.frame.remove(self.dialogue)
        self.frame.add(self.save_dialogue)
        self.save_dialogue._set_x(self.mx)
        self.save_dialogue._set_y(self.my)

    def _exit(self, button):
        """Exit the map editor."""
        os.remove(LOCK_FILE)
        self.write_config()
        pyglet.app.exit()

    def _redo_insert(self, data):
        self.insert_tile_dict(data)

    def _undo_insert(self, index):
        tile, data = self.tiles[index]
        tile.delete()
        self.tiles[index] = None
        self._redo_actions.append((self._redo_insert, data))

    def __init__(self,
            batch_browser=None,
            texture_browser=None,
            preview_window=None,
            map_name=None):
        '''Initializes the grid.'''
        super(MapEditor, self).__init__(WIDTH, HEIGHT)

        # Create the grids, setup the index, and fixate the sizes
        self.grid_index = 0
        self.grid_sizes = [8, 16, 32, 64, 128, 256]
        self.grids = [load_image(GRID_RES_PATH % x) for x in self.grid_sizes]
        self.grid = TileableSprite(self.grids[self.grid_index],
                x=0, y=0, tile_width=self.width, tile_height=self.height)
        self.grid.opacity = self.grid_opacity
        self.preview_grid = TileableSprite(
                load_image(GRID_RES_PATH % 'preview'),
                x=0, y=0, tile_width=self.width, tile_height=self.height)

        # Key handlers
        self.keys = key.KeyStateHandler()
        self.push_handlers(self.keys)

        # Camera
        self.camera = camera.FlatCamera(0, 0, self.width, self.height)

        self._group_label = PygletLabel("Texture Priority: %s" % self._group,
                font_name='Times New Roman',
                font_size=12,
                x=0, y=2)

        # Link together the other browsers
        self.batch_editor = batch_browser
        self.texture_editor = texture_browser
        self.preview_window = preview_window
        self.theme = Theme(THEME_RES_PATH)
        self.frame = Frame(self.theme, window=self,
                w=640, h=480, camera=self.camera)
        self.push_handlers(self.frame)

        # Reloads
        self.reload_tiles()

        # Load our config
        self.load_config()

        #  _________________________
        # |                         |
        # | Save Path: _______      |
        # |_________________________|
        #
        self.save_dialogue = Dialogue('Save', x=100, y=200,
            content=HLayout(w=200, children=[
                Label('Save Path:'),
                TextInput(w=200, text='', action=self._save_action)]))

        #  _________________________
        # |                         |
        # | Load Path: _______      |
        # |_________________________|
        #
        self.load_dialogue = Dialogue('Load', x=100, y=200,
            content=HLayout(w=200, children=[
                Label('Load Path:'),
                TextInput(w=200, text='', action=self._load_action)]))

        #  _________________________
        # |        Menu             |
        # |-------------------------| # | Editor/File             |
        # |\/\/\/\/\/\/\/\/\/\/\/\/\|
        # |   + [New]               |
        # |   + [Load]              |
        # |   + [Save]              |
        # |   + [Exit]              |
        # |   + [Reload Textures]   |
        # |\/\/\/\/\/\/\/\/\/\/\/\/\|
        # | Options                 |
        # |\/\/\/\/\/\/\/\/\/\/\/\/\|
        # | + Autosave []           |
        # | + Enable Preview []     |
        # | + Toggle Preview Grid []|
        # | + Toggle Grid []        |
        # | + Snap Grid []          |
        # | + Grid Opacity [-o-----]|
        # | + Grid Size [-----o----]|
        # |\/\/\/\/\/\/\/\/\/\/\/\/\|
        # | Spawn                   |
        # |\/\/\/\/\/\/\/\/\/\/\/\/\|
        # | + <Entity List>         |
        # |_________________________|
        self.dialogue = Dialogue('Menu', x=100, y=200,
            content=VLayout(autosizex=True, hpadding=0, children=[
                FoldingBox('Editor / File', collapsed=True,
                    content=VLayout(children=[
                        Button('New ', action=self._new_map),
                        Button('Load', action=self._load_map),
                        Button('Save', action=self._save_map),
                        Button('Exit', action=self._exit),
                        Button('Reload Textures', action=self._exit)
                    ])
                ),
                FoldingBox('Options', collapsed=True,
                    content=VLayout(children=[
                        Checkbox('Autosave', h=100,
                                value=self.grid.visible,
                                action=self._set_grid_visibility,
                                think=self._grid_toggle_update),
                        Checkbox('Enable Preview', h=100,
                                value=True,
                                action=self._toggle_preview),
                        Checkbox('Toggle Preview Grid', h=100,
                                value=self.preview_grid.visible,
                                action=self._set_preview_grid_visibility),
                        Checkbox('Toggle Grid', h=100,
                                value=self.grid.visible,
                                action=self._set_grid_visibility,
                                think=self._grid_toggle_update),
                        Checkbox('Pan Freely', h=100,
                                value=self.pan_free,
                                action=self._set_pan_free,
                                think=self._grid_toggle_pan),
                        Checkbox('Snap Grid', h=100,
                                value=self.snap,
                                action=self._set_grid_snap,
                                think=self._grid_toggle_snap),
                        HLayout(children=[
                            Label('Grid Opacity', halign='right'),
                            Slider(w=100, min=0, max=255,
                                    action=self._set_grid_opacity,
                                    value=self.grid.opacity,
                                    think=self._grid_opacity_update)
                        ]),
                        HLayout(children=[
                            Label('Grid Size', halign='right'),
                            Slider(w=100, min=0, max=5,
                                    action=self._set_grid_size,
                                    value=self.grid_index,
                                    think=self._grid_size_update)
                        ])

                    ])
                ),
                FoldingBox('Spawn', collapsed=False,
                    content=VLayout(children=self._generate_entity_list())
                )


            ]))

        # If we crashed, then load the old map
        if os.path.exists(LOCK_FILE) and map_name is None:
            print 'MapEditor crashed last time? Loading autosave.'
            self.load_map(AUTOSAVE_MAP)
        elif map_name:
            self.load_map(map_name)

        # Create our lock
        l = file(LOCK_FILE, 'w')
        l.close()

        # Schedule the autosaving
        schedule_interval(self._autosave_scheduler, AUTOSAVE_TIME)

    def load_config(self):
        try:
            cfg = json.load(open(CONFIG_PATH))
            self.pan_sens = cfg.get('pan_sens', self.pan_sens)
            self.pan_size = cfg.get('pan_size', self.pan_size)
            self.pan_enable = cfg.get('pan_enable', self.pan_enable)
            self.pan_free = cfg.get('pan_free', self.pan_free)
            self.grid_opacity = cfg.get('grid_opacity', self.grid_opacity)
            self.snap = cfg.get('snap', self.snap)
            self.grid_index = cfg.get('grid_index', self.grid_index)
            enable_preview = cfg.get('enable_preview', True)
            self.toggle_preview(enable_preview)
            enable_preview_grid = cfg.get('enable_preview_grid', True)
            self.preview_grid.visible = enable_preview_grid
            enable_grid = cfg.get('enable_grid', True)
            self.grid.visible = enable_grid
        except IOError:
            print 'Could not find config using defaults and creating config.'
            self.write_config()

    def write_config(self):
        enable_preview = True
        if self.preview_window:
            enable_preview = self.preview_window._visible

        config = {
                'pan_sens': self.pan_sens,
                'pan_size': self.pan_size,
                'pan_enable': self.pan_enable,
                'pan_free': self.pan_free,
                'grid_opacity': self.grid_opacity,
                'snap': self.snap,
                'grid_index': self.grid_index,
                'enable_preview': enable_preview,
                'enable_preview_grid': self.preview_grid.visible,
                'enable_grid': self.grid.visible
        }

        json.dump(config, open(CONFIG_PATH, 'w'), indent=4)

    def reload_tiles(self):
        # Load all the necessary materials
        files = os.listdir(MATERIAL_PATH)
        self.tile_batch = []
        for f in files:
            name, ext = f.split(".")
            if ext != 'mtr':
                continue
            mtrs = json.load(open("%s/%s" % (MATERIAL_PATH, f)))
            for item in mtrs:
                if mtrs[item].get('entity'):
                    cfg = mtrs[item]
                    entity = str(cfg['entity'])
                    im = __import__('engine.entities', fromlist=[entity])
                    im = im.__dict__[entity]
                    self._entities[item] = im
                    mat = Material(mtrs[item], item)
                    self._mtr[item] = mat

                else:
                    mat = Material(mtrs[item], item)
                    self.tile_batch.append(mat)
                    self._mtr[item] = mat

    def increase_group_priority(self):
        if self.selected:
            self._group_label.text = "Selected Priority: %s" % (
                    self.selected.group.order)
            self.selected.group = pyglet.graphics.OrderedGroup(
                    self.selected.group.order + 1)
        else:
            self._group += 1
            self.group = pyglet.graphics.OrderedGroup(self._group)
            self._group_label.text = "Texture Priority: %s" % self._group
            if self.inserting_tile:
                self.inserting_tile.group = self.group
            elif self.inserting_entity:
                self.inserting_entity.group = self.group

    def decrease_group_priority(self):
        if self.selected:
            self._group_label.text = "Selected Priority: %s" % (
                    self.selected.group.order)
            self.selected.group = pyglet.graphics.OrderedGroup(
                    self.selected.group.order - 1)
        else:
            self._group -= 1
            self.group = pyglet.graphics.OrderedGroup(self._group)
            self._group_label.text = "Texture Priority: %s" % self._group
            if self.inserting_tile:
                self.inserting_tile.group = self.group
            elif self.inserting_entity:
                self.inserting_entity.group = self.group

    def show_selected_priority(self):
        if self.selected:
            self._group_label.text = "Selected Priority: %s" % (
                    self.selected.group.order)

    def show_general_priority(self):
        self._group_label.text = "Texture Priority: %s" % self._group

    def insert_entity(self, entity_name, x, y, height):
        """Inserts a given entity at a (x, y, height)"""
        pass

    def insert_tile(self, tile, conf):
        """Inserts a given tile at a (x, y, height)"""
        px, py = tile.x, tile.y
        width, height = tile.tile_width, tile.tile_height
        batch = self.batches[self.active_height][1]
        img = self.tile_batch[self.inserting_tile_index].image
        self.last_tile = TileableSprite(tile._texture, px, py,
                tile_width=width, tile_height=height,
                group=self.group, batch=batch)
        order = (self.last_tile, {
            'x': px, 'y': py, 'w': width, 'h': height,
            'p': self.group.order,
            'z': self.active_height, 'm': conf
        })
        self.tiles.append(order)
        self._redo_actions = []
        self._undo_actions.append((self._undo_insert, len(self.tiles) - 1))

        if (abs(px) + width) > self.map_width:
            self.map_width = abs(px) + width

        if (abs(py) + height) > self.map_height:
            self.map_height = abs(py) + height


    def insert_tile_dict(self, tile):
        """Inserts a given tile at a (x, y, height)"""
        mat_name = tile['m']
        x, y, z, p = tile['x'], tile['y'], tile['z'], tile['p']
        w, h = tile['w'], tile['h']
        mat = self._mtr[mat_name]
        batch = self.batches[z][1]
        img = mat.image
        tile_group = pyglet.graphics.OrderedGroup(p)
        self.last_tile = TileableSprite(img, x, y,
                tile_width=w, tile_height=h,
                group=tile_group, batch=batch)
        self.tiles.append((self.last_tile, {
            'x': x, 'y': y, 'w': w, 'h': h,
            'z': z, 'p': p, 'm': mat_name
        }))
        self._undo_actions.append((self._undo_insert, len(self.tiles) - 1))

        if (abs(x) + w) > self.map_width:
            self.map_width = abs(x) + w

        if (abs(y) + h) > self.map_height:
            self.map_height = abs(y) + h 


    def insert_mode(self, index):
        """Puts the map editor into insert mode for new tiles.
        This functionally will most likely be used from the texture editor
        and not directly as it requires knowledge of the texture index."""
        tile_img = self.tile_batch[index].image
        tile_conf = self.tile_batch[index].name
        batch = self.batches[self.active_height][1]
        self.inserting_tile = TileableSprite(tile_img, 0, 0,
                group=self.group, batch=batch)
        self.inserting_tile_conf = tile_conf
        self.inserting_tile_index = index
        self.inserting_tile.opacity = 155

    def increase_grid(self):
        """Increase the grid by a fixed interval."""
        if (self.grid_index + 1) == len(self.grids):
            return
        self.grid_index += 1
        self.grid.image = self.grids[self.grid_index]
        self._grid_dirty = True

    def decrease_grid(self):
        """Decrease the gid by a fixed interval."""
        if self.grid_index == 0:
            return
        self.grid_index -= 1
        self.grid.image = self.grids[self.grid_index]
        self._grid_dirty = True

    def on_resize(self, width, height):
        self.grid.tile_width = width
        self.grid.tile_height = height
        self.preview_grid.tile_width = width
        self.preview_grid.tile_height = height
        self.camera.width = width
        self.camera.height = height

    def on_mouse_enter(self, x, y):
        self.pan_enable = True

    def on_mouse_leave(self, x, y):
        self.pan_enable = False

    def on_mouse_motion(self, x, y, dx, dy):
        """Defines the on_mouse_motion event for the editor window."""
        self.mx, self.my = x, y

        # First case, if we are inserting
        if self.inserting_tile or self.inserting_entity:
            if self.inserting_tile:
                tile = self.inserting_tile
            else:
                tile = self.inserting_entity
            inc = self.grid_sizes[self.grid_index]

            # If we are not snapping, don't worry about handling the extra
            # logic in where the next tile placement should be. Instead just
            # Roughly calculate the center.
            if not self.snap:
                tile.x = -self.camera.x + x - tile.tile_width / 2.0
                tile.y = -self.camera.y + y - tile.tile_height / 2.0
                return

            # These are the margin boundaries that the user will neeed to
            # cross in order to trigger the tile movement.
            margin_x_inc = (tile.x + tile.tile_width / 2.0) + inc
            margin_x_dec = (tile.x + tile.tile_width / 2.0) - inc
            margin_y_inc = (tile.y + tile.tile_height / 2.0) + inc
            margin_y_dec = (tile.y + tile.tile_height / 2.0) - inc

            tx = tile.x
            ty = tile.y

            ox, oy = x, y

            x = -self.camera.x + x
            y = -self.camera.y + y

            # Various rules for all 8-directions.
            if x > margin_x_inc and y > margin_y_inc:
                tile.x += inc
                tile.y += inc
            elif x > margin_x_inc and y < margin_y_dec:
                tile.x += inc
                tile.y -= inc
            elif x < margin_x_dec and y > margin_y_inc:
                tile.x -= inc
                tile.y += inc
            elif x < margin_x_dec and y < margin_y_dec:
                tile.x -= inc
                tile.y -= inc
            elif x > margin_x_inc:
                tile.x += inc
            elif x < margin_x_dec:
                tile.x -= inc
            elif y > margin_y_inc:
                tile.y += inc
            elif y < margin_y_dec:
                tile.y -= inc

            if tx != tile.x or ty != tile.y:
                # This may look stupid, but it forces tiles to align to
                # the proper location because the way on_mouse_drag works
                # TODO(justinvh): Possible infinite recursion?
                self.on_mouse_motion(ox, oy, dx, dy)

    def on_mouse_press(self, x, y, button, modifiers):
        """Handles the logic for on_mouse_press actions."""
        # If the right mouse button is clicked
        if button == 4:
            # If the dialogue already exists, then there is no point
            # in readding the scene, so this ensures it only happens once.
            if not self.dialogue.parent:
                self.frame.add(self.dialogue)

            # Create the necessary offsets for the dialogue and then
            # add the dialogue frames to the renderer to be drawn.
            self.dialogue._set_x(x)
            self.dialogue._set_y(y)
            return

        # If we already have a dialogue parent, then there is no need
        # to do the rest of the logic handling a new parent.
        if self.dialogue.parent:
            return

        # If we are not inserting and nothing is selected
        # then we are probably selecting.
        if not self.inserting_tile:
            # Iterate over the possible tiles to be clicked
            # TODO(justinvh): Stupid way.
            self.dragging = False

            # First check the tile we are currently selected
            if (self.selected and not self.keys[_MOD_SHIFT] and
                x > self.selected.x and y > self.selected.y and
                x < (abs(self.selected.tile_width) + self.selected.x) and
                y < (abs(self.selected.tile_height) + self.selected.y)):
                    return

            for i, (tile, conf) in enumerate(self.tiles):
                if (x > tile.x and y > tile.y and
                    x < (abs(tile.tile_width) + tile.x) and
                    y < (abs(tile.tile_height) + tile.y)):

                    # Are we holding SHIFT, if so then we are cycling
                    # through tiles.
                    if self.keys[_MOD_SHIFT] and self.selected == tile:
                        continue

                    if self.selected:
                        self.selected.opacity = 255

                    # Select a new tile
                    self.selected = tile
                    self.selected_tile_index = i
                    self.selected.opacity = 155
                    self.show_selected_priority()
                    return
        elif (self.inserting_tile and
                not self.dragging and not self._possible_drag):
            self.insert_tile(self.inserting_tile, self.inserting_tile_conf)

    def on_mouse_release(self, x, y, button, modifiers):
        """Handles logic when we release our mouse."""
        self.dragging = False

    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        """Handles logic on mouse drag."""
        x = -self.camera.x + x
        y = -self.camera.y + y
        if (self.inserting_entity
            or (self.inserting_tile and modifiers & key.MOD_SHIFT)):

            if self.inserting_entity:
                tile = self.inserting_entity
            else:
                tile = self.inserting_tile

            # Snap incremement
            inc = self.grid_sizes[self.grid_index]

            # If we are not dragging, then we need to see if we are capable
            # of dragging because otherwise this is pointless
            if not self.dragging:
                if (x > tile.x and y > tile.y and
                    x < (tile.tile_width + tile.x) and
                    y < (tile.tile_height + tile.y)):
                    self.dragging = True
                else:
                    self.dragging = False
                    return

            # The easy case is if we are not snapping. Then the tile can
            # just freely move without worry about the margins.
            if not self.snap:
                tile.x = x + tile.tile_width / 2.0
                tile.y = y + tile.tile_height / 2.0
                return

            # Our directional margins
            margin_x_inc = (tile.x + tile.tile_width / 2.0) + inc
            margin_x_dec = (tile.x + tile.tile_width / 2.0) - inc
            margin_y_inc = (tile.y + tile.tile_height / 2.0) + inc
            margin_y_dec = (tile.y + tile.tile_height / 2.0) - inc

            tw = tile.tile_width
            th = tile.tile_height

            # The eight directions
            if x > margin_x_inc and y > margin_y_inc:
                tile.tile_width += inc
                tile.tile_height += inc
            elif x > margin_x_inc and y < margin_y_dec:
                tile.tile_width += inc
                tile.tile_height -= inc
            elif x < margin_x_dec and y > margin_y_inc:
                tile.tile_width -= inc
                tile.tile_height += inc
            elif x < margin_x_dec and y < margin_y_dec:
                tile.tile_width -= inc
                tile.tile_height -= inc
            elif x > margin_x_inc:
                tile.tile_width += inc
            elif x < margin_x_dec:
                tile.tile_width -= inc
            elif y > margin_y_inc:
                tile.tile_height += inc
            elif y < margin_y_dec:
                tile.tile_height -= inc

            #if tw != tile.tile_width or th != tile.tile_height:
                # This may look stupid, but it forces tiles to align to
                # the proper location because the way on_mouse_drag works
                # TODO(justinvh): Possible infininite recursion
                #self.on_mouse_drag(x, y, dx, dy, buttons, modifiers)
            return

        # Typically if we are not inserting and nothing is selected
        # then nothing should happen, but if we drag with something
        # selected then we are moving tiles.
        if not self.inserting_tile:
            if self.selected is None:
                return

            # Snap incremement
            inc = self.grid_sizes[self.grid_index]
            tile = self.selected

            # If we are not dragging, then we need to see if we are capable
            # of dragging because otherwise this is pointless
            if not self.dragging:
                if (x > tile.x and y > tile.y and
                    x < (tile.tile_width + tile.x) and
                    y < (tile.tile_height + tile.y)):
                    self.dragging = True
                else:
                    self.dragging = False
                    return

            # The easy case is if we are not snapping. Then the tile can
            # just freely move without worry about the margins.
            if not self.snap:
                tile.x = x + tile.tile_width / 2.0
                tile.y = y + tile.tile_height / 2.0
                return

            # Our directional margins
            margin_x_inc = (tile.x + tile.tile_width / 2.0) + inc
            margin_x_dec = (tile.x + tile.tile_width / 2.0) - inc
            margin_y_inc = (tile.y + tile.tile_height / 2.0) + inc
            margin_y_dec = (tile.y + tile.tile_height / 2.0) - inc

            tx = tile.x
            ty = tile.y

            # The eight directions
            if x > margin_x_inc and y > margin_y_inc:
                tile.x += inc
                tile.y += inc
            elif x > margin_x_inc and y < margin_y_dec:
                tile.x += inc
                tile.y -= inc
            elif x < margin_x_dec and y > margin_y_inc:
                tile.x -= inc
                tile.y += inc
            elif x < margin_x_dec and y < margin_y_dec:
                tile.x -= inc
                tile.y -= inc
            elif x > margin_x_inc:
                tile.x += inc
            elif x < margin_x_dec:
                tile.x -= inc
            elif y > margin_y_inc:
                tile.y += inc
            elif y < margin_y_dec:
                tile.y -= inc

            if tx != tile.x or ty != tile.y:
                # This may look stupid, but it forces tiles to align to
                # the proper location because the way on_mouse_drag works
                # TODO(justinvh): Possible infininite recursion
                self.on_mouse_drag(x, y, dx, dy, buttons, modifiers)
        elif self.last_tile:
            self.on_mouse_motion(x, y, dx, dy)
            tile = self.last_tile
            margin_x_inc = (tile.x + tile.tile_width + tile.tile_width / 2.0)
            margin_x_dec = (tile.x - tile.tile_width / 2.0)
            margin_y_inc = (tile.y + tile.tile_height + tile.tile_height / 2.0)
            margin_y_dec = (tile.y - tile.tile_height / 2.0)

            tx = tile.x
            ty = tile.y

            insert = False

            if x > margin_x_inc and y > margin_y_inc:
                insert = True
            elif x > margin_x_inc and y < margin_y_dec:
                insert = True
            elif x < margin_x_dec and y > margin_y_inc:
                insert = True
            elif x < margin_x_dec and y < margin_y_dec:
                insert = True
            elif x > margin_x_inc:
                insert = True
            elif x < margin_x_dec:
                insert = True
            elif y > margin_y_inc:
                insert = True
            elif y < margin_y_dec:
                insert = True

            if insert:
                self.insert_tile(self.inserting_tile, self.inserting_tile_conf)

    def on_key_release(self, symbol, modifiers):
        """The release bindings."""
        if modifiers & key.MOD_SHIFT:
            self._possible_drag = False
        elif modifiers & key.MOD_CTRL:
            self._pan_scheduled = False
            pyglet.clock.unschedule(self._pan_scheduler)

    def on_key_press(self, pressed, modifiers):
        """All the key bindings."""
        # Save the map
        if pressed == key.S and modifiers == key.MOD_CTRL:
            self._save_map(pressed)
        # Lock the grid
        elif pressed == key.L:
            self.pan_free = not self.pan_free
        # Increase the grid
        elif pressed == key.EQUAL:
            if self.selected:
                pass
                # increase rendering priority
                # find out what tiles are at this location and then move
                # the tile up in an increasing fashion
            self.increase_grid()
        # Decrease the grid
        elif pressed == key.MINUS:
            self.decrease_grid()
        # Exit out of active menus or events
        elif pressed == key.ESCAPE:
            if self.save_dialogue.parent:
                self.frame.remove(self.save_dialogue)
                return
            if self.dialogue.parent:
                self.frame.remove(self.dialogue)
                return
            if self.selected:
                self.selected.opacity = 255
                self.selected = None
            if self.inserting_tile:
                self.inserting_tile.delete()
                self.inserting_tile = None
            if self.inserting_entity:
                self.inserting_entity.delete()
                self.inserting_entity = None
            self.show_general_priority()
        # Delete selected objects
        elif pressed == key.DELETE:
            if self.selected:
                self.selected.delete()
                del self.tiles[self.selected_tile_index]
                self.selected = None
                self.show_general_priority()
        # Move an object up
        elif pressed == key.UP:
            if self.selected:
                inc = self.grid_sizes[self.grid_index]
                self.selected.y += inc
        # Move an object down
        elif pressed == key.DOWN:
            if self.selected:
                inc = self.grid_sizes[self.grid_index]
                self.selected.y -= inc
        # Move an object right
        elif pressed == key.RIGHT:
            if self.selected:
                inc = self.grid_sizes[self.grid_index]
                self.selected.x += inc
        # Move an object left
        elif pressed == key.LEFT:
            if self.selected:
                inc = self.grid_sizes[self.grid_index]
                self.selected.x -= inc
        # Move objects to new rendering batches
        elif pressed >= key.EXCLAMATION and pressed <= key.PARENRIGHT:
            if self.selected:
                self.selected.batch = (
                        self.batches[key.EXCLAMATION - pressed][1])
        # Increase the grid opacity
        elif pressed == key.PLUS and modifiers & key.MOD_CTRL:
            if self.grid.opacity < 255:
                self.grid.opacity += 25
                self.grid_opacity = self.grid.opacity
            else:
                self.grid.opacity = 255
            self._grid_dirty = True
        # Decrease group priority
        elif pressed == key.J:
            self.decrease_group_priority()
        # Increase group priority
        elif pressed == key.K:
            self.increase_group_priority()
        # Decrease the grid opacity
        elif pressed == key.UNDERSCORE and modifiers & key.MOD_CTRL:
            if self.grid.opacity > 0:
                self.grid.opacity -= 25
                self.grid_opacity = self.grid.opacity
            else:
                self.grid.opacity = 0
            self._grid_dirty = True
        # Snap the grid
        elif pressed == key.S:
            self._grid_dirty = True
            self.snap = not self.snap
            # Snap to nearest grid
            if self.snap and self.inserting_tile:
                x, y = self.inserting_tile.x, self.inserting_tile.y
                self.inserting_tile.x = 0
                self.inserting_tile.y = 0
                self.on_mouse_motion(self.mx, self.my, 0, 0)
            elif self.snap and self.inserting_entity:
                x, y = self.inserting_entity.x, self.inserting_entity.y
                self.inserting_entity.x = 0
                self.inserting_entity.y = 0
                self.on_mouse_motion(self.mx, self.my, 0, 0)
        # Toggle the grid
        elif pressed == key.G:
            self.grid.visible = not self.grid.visible
            self._grid_dirty = True
        # Undo
        elif pressed == key.Z and modifiers & key.MOD_CTRL:
            if len(self._undo_actions):
                fun, arg = self._undo_actions[-1]
                fun(arg)
                del self._undo_actions[-1]
        # Redo
        elif pressed == key.R and modifiers & key.MOD_CTRL:
            if len(self._redo_actions):
                fun, arg = self._redo_actions[-1]
                fun(arg)
                del self._redo_actions[-1]

    def update_batches(self, lst):
        """Update the rendering batches."""
        for i, case in enumerate(lst):
            self.batches[i][0] = case

    def on_draw(self):
        """Handles all the drawing."""

        if self.keys[_MOD_SHIFT]:
            self._possible_drag = True

        if self.keys[_MOD_CTRL] and not self._pan_scheduled:
            self._pan_scheduled = True
            schedule_interval(self._pan_scheduler, 1/60.0)


        glClearColor(0.10, 0.10, 0.10, 1.0)
        self.clear()
        self.camera.project()
        # If the batches are enabled, then draw them
        for case, batch in self.batches:
            if case is True:
                batch.draw()
        self.grid.draw()
        self.preview_grid.draw()
        self.frame.draw()
        self._group_label.draw()
        self._grid_dirty = False
